package lxs.swift.collector.sync;

import lxs.swift.collector.wrap.Option;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;

public class Mutex<T> {
    private static final Logger log = LoggerFactory.getLogger(Mutex.class);
    private transient T value;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public Mutex(T value) {
        this.value = value;
    }

    public Condition condition() {
        return lock.newCondition();
    }

    public T get() {
        lock.lock();
        try {
            return value;
        } finally {
            unlock();
        }
    }

    public T tryGet(long wait, @NotNull TimeUnit unit) {
        Objects.requireNonNull(unit);
        tryLock(wait, unit);
        try {
            return value;
        } finally {
            unlock();
        }
    }

    public <R> R map(@NotNull Function<T, R> mapper) {
        Objects.requireNonNull(mapper);
        lock.lock();
        try {
            return mapper.apply(value);
        } finally {
            unlock();
        }
    }

    public <R> R tryMap(long wait, @NotNull TimeUnit unit, @NotNull Function<T, R> mapper) {
        Objects.requireNonNull(unit);
        Objects.requireNonNull(mapper);
        try {
            tryLock(wait, unit);
            return mapper.apply(value);
        } finally {
            unlock();
        }
    }

    public T set(@NotNull Function<T, T> mapper) {
        Objects.requireNonNull(mapper);
        lock.lock();
        try {
            this.value = mapper.apply(value);
            return this.value;
        } finally {
            unlock();
        }
    }

    public T trySet(long wait, @NotNull TimeUnit unit, @NotNull Function<T, T> mapper) {
        Objects.requireNonNull(unit);
        Objects.requireNonNull(mapper);
        try {
            tryLock(wait, unit);
            this.value = mapper.apply(value);
            return this.value;
        } finally {
            unlock();
        }
    }

    /**
     * 如果已经有其他线程正在执行则立即返回None
     */
    public Option<T> softSet(@NotNull Function<T, T> mapper) {
        Objects.requireNonNull(mapper);
        if (lock.isLocked()) return Option.none();
        T t = this.set(mapper);
        return Option.some(t);
    }

    public T replace(@NotNull Function<T, T> mapper) {
        Objects.requireNonNull(mapper);
        lock.lock();
        try {
            T old = this.value;
            this.value = mapper.apply(value);
            return old;
        } finally {
            unlock();
        }
    }

    public T tryReplace(long wait, @NotNull TimeUnit unit, @NotNull Function<T, T> mapper) {
        Objects.requireNonNull(unit);
        Objects.requireNonNull(mapper);
        try {
            tryLock(wait, unit);
            T old = this.value;
            this.value = mapper.apply(value);
            return old;
        } finally {
            unlock();
        }
    }

    /**
     * 如果已经有其他线程正在执行则立即返回None
     */
    public Option<T> softReplace(@NotNull Function<T, T> mapper) {
        Objects.requireNonNull(mapper);
        if (lock.isLocked()) return Option.none();
        T old = this.value;
        this.value = this.set(mapper);
        return Option.some(old);
    }

    public void use(@NotNull Consumer<T> consumer) {
        Objects.requireNonNull(consumer);
        lock.lock();
        try {
            consumer.accept(value);
        } finally {
            unlock();
        }
    }

    public void tryUse(long wait, @NotNull TimeUnit unit, @NotNull Consumer<T> consumer) {
        Objects.requireNonNull(consumer);
        try {
            tryLock(wait, unit);
            consumer.accept(value);
        } finally {
            unlock();
        }
    }

    public boolean tryUse(@NotNull Consumer<T> consumer) {
        Objects.requireNonNull(consumer);
        //如果已经有其他线程正在执行则阻塞并等待其他人返回值
        if (lock.isLocked()) {
            return false;
        }
        this.use(consumer);
        return true;
    }

    public boolean isLocked() {
        return lock.isLocked();
    }

    public void unlockAwait() {
        try {
            condition.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private void unlock() {
        if (!lock.isLocked()) return;
        condition.signalAll();
        lock.unlock();
    }

    private void tryLock(long wait, @NotNull TimeUnit unit) {
        try {
            boolean b = lock.tryLock(wait, unit);
            if (!b) throw new RuntimeException("get value failed: wait time out");
        } catch (InterruptedException e) {
            unlock();
            throw new RuntimeException(e);
        }
    }
}
